package com.example.literj.kt.exceptions

import java.io.PrintStream
import java.io.PrintWriter
import java.util.*

class CompositeException(vararg mExceptions: Throwable) : RuntimeException() {
    constructor(errors: MutableList<Throwable?>?) : this() {
        val deDupedExceptions: MutableSet<Throwable?> = LinkedHashSet()
        if (errors != null) {
            for (ex in errors) {
                if (ex is CompositeException) {
                    deDupedExceptions.addAll(ex.exceptions)
                } else if (ex != null) {
                    deDupedExceptions.add(ex)
                } else {
                    deDupedExceptions.add(NullPointerException("Throwable was null!"))
                }
            }
        } else {
            deDupedExceptions.add(NullPointerException("errors was null"))
        }
        require(deDupedExceptions.isNotEmpty()) { "errors is empty" }
        val localExceptions: List<Throwable?> = ArrayList(deDupedExceptions)
        exceptions = Collections.unmodifiableList(localExceptions)
        mMessage = exceptions.size.toString() + " exceptions occurred. "
    }

    private var exceptions = listOf<Throwable?>().plus(mExceptions)
    private var mMessage: String? = null

    override val cause: Throwable?
        get() = getCauseSelf()
    override val message: String?
        get() = mMessage

    @Synchronized
    fun getCauseSelf(): Throwable? {
        var causeSelf = cause
        if (causeSelf == null) {
            val separator = System.getProperty("line.separator")
            if (exceptions.size > 1) {
                val seenCauses: MutableMap<Throwable, Boolean> = IdentityHashMap()
                val aggregateMessage = StringBuilder()
                aggregateMessage.append("Multiple exceptions (").append(exceptions.size).append(")")
                    .append(separator)
                for (inner in exceptions) {
                    var depth = 0
                    while (inner != null) {
                        for (i in 0 until depth) {
                            aggregateMessage.append("  ")
                        }
                        aggregateMessage.append("|-- ")
                        aggregateMessage.append(inner.javaClass.canonicalName).append(": ")
                        val innerMessage = inner.message
                        if (innerMessage != null && innerMessage.contains(separator)) {
                            aggregateMessage.append(separator)
                            for (line in innerMessage.split(separator).toTypedArray()) {
                                for (i in 0 until depth + 2) {
                                    aggregateMessage.append("  ")
                                }
                                aggregateMessage.append(line).append(separator)
                            }
                        } else {
                            aggregateMessage.append(innerMessage)
                            aggregateMessage.append(separator)
                        }
                        for (i in 0 until depth + 2) {
                            aggregateMessage.append("  ")
                        }
                        val st = inner.stackTrace
                        if (st.isNotEmpty()) {
                            aggregateMessage.append("at ").append(st[0]).append(separator)
                        }
                        var innerCause = inner
                        if (!seenCauses.containsKey(inner)) {
                            seenCauses[inner] = true
                            innerCause = inner.cause
                            depth++
                        } else {
                            innerCause = inner.cause
                            if (innerCause != null) {
                                for (i in 0 until depth + 2) {
                                    aggregateMessage.append("  ")
                                }
                                aggregateMessage.append("|-- ")
                                aggregateMessage.append("(cause not expanded again) ")
                                aggregateMessage.append(innerCause.javaClass.canonicalName)
                                    .append(": ")
                                aggregateMessage.append(innerCause.message)
                                aggregateMessage.append(separator)
                            }
                            break
                        }
                    }
                }
                causeSelf = ExceptionOverview(aggregateMessage.toString().trim { it <= ' ' })
            } else {
                causeSelf = exceptions[0]
            }
        }
        return causeSelf
    }

    fun printStackTrace() {
        printStackTrace(System.err)
    }

    fun printStackTrace(s: PrintStream) {
        printStackTrace(WrappedPrintStream(s))
    }

    fun printStackTrace(s: PrintWriter) {
        printStackTrace(WrappedPrintWriter(s))
    }

    private fun printStackTrace(output: PrintStreamOrWriter) {
        output.append(this).append("\n")
        for (myStackElement in stackTrace) {
            output.append("\tat ").append(myStackElement).append("\n")
        }
        var i = 1
        for (ex in exceptions) {
            output.append("  ComposedException ").append(i).append(" :\n")
            appendStackTrace(output, ex, "\t")
            i++
        }
        output.append("\n")
    }

    private fun appendStackTrace(output: PrintStreamOrWriter, ex: Throwable?, prefix: String) {
        output.append(prefix).append(ex).append('\n')
        for (stackElement in ex!!.stackTrace) {
            output.append("\t\tat ").append(stackElement).append('\n')
        }
        if (ex.cause != null) {
            output.append("\tCaused by: ")
            appendStackTrace(output, ex.cause, "")
        }
    }

    internal abstract class PrintStreamOrWriter {
        abstract fun append(o: Any?): PrintStreamOrWriter
    }

    internal class WrappedPrintStream(private val printStream: PrintStream) :
        PrintStreamOrWriter() {
        override fun append(o: Any?): WrappedPrintStream {
            printStream.print(o)
            return this
        }
    }

    internal class WrappedPrintWriter(private val printWriter: PrintWriter) :
        PrintStreamOrWriter() {
        override fun append(o: Any?): WrappedPrintWriter {
            printWriter.print(o)
            return this
        }
    }

    internal class ExceptionOverview(message: String?) : java.lang.RuntimeException(message) {
        @Synchronized
        override fun fillInStackTrace(): Throwable {
            return this
        }
    }
}